package problems;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import ec.FitnessEvaluator;
import ec.GA;
import ec.Generation;
import ec.Solution;

public abstract class Problem {

	/**
	 * Perform a evolutionary computation search, in this case a GA, on the
	 * problem.
	 * 
	 * @param data_path
	 *            the path to store the fitness data for each run
	 * @param num_runs
	 *            the number of runs to do
	 * @param num_generations
	 *            the number of generations per run
	 * @param population_size
	 *            the number of solutions in a population
	 * @param tournament_size
	 *            TODO
	 * @param mutation_probability
	 *            TODO
	 * @param crossover_probability
	 *            TODO
	 * @param elites_percentage
	 *            TODO
	 */
	public void ec(String data_path, int num_runs, int num_generations,
			int population_size, int tournament_size, int mutation_probability,
			int crossover_probability, int elites_percentage,
			boolean use_pruning) {

		Writer fitness_file = null;
		Writer len_file = null;
		
		// For each run
		for (int run = 0; run < num_runs; run++) {
			try {
				long run_start = System.currentTimeMillis();

				// Create a separate file for each run
				String path = data_path
						+ String.format("ec/run%1$04d/",
								(int) (Math.random() * 9999));
				File file = new File(path);
				file.mkdirs();
				fitness_file = new BufferedWriter(new FileWriter(path + "/fitness.txt"));
				len_file = new BufferedWriter(new FileWriter(path + "/len.txt"));

				// Create our initial population of random individuals
				List<Solution> initial_population = new ArrayList<Solution>(
						population_size);
				for (int i = 0; i < population_size; i++) {
					initial_population.add(createRandomSolution());
				}

				FitnessEvaluator fitness_evaluator = getFitnessEvaluator();

				// Create a GA controller
				GA ga = new GA(initial_population, fitness_evaluator,
						tournament_size, mutation_probability,
						crossover_probability, elites_percentage);
				ga.usePruning(use_pruning);

				System.out.print("\nRun ");
				System.out.print(run);
				System.out.print(":\n");

				// For each generation
				for (int cur_gen = 0; cur_gen < num_generations; cur_gen++) {
					Generation gen = ga.runGeneration();

					if (cur_gen % 50 == 0) {
						System.out.println(gen);
					}

					// Write out the fitness values to the file
					double[] fitness = gen.getFitnessValues();
					for (int i = 0; i < fitness.length; i++) {
						fitness_file.write(String.valueOf(fitness[i]));						
						fitness_file.write("\t");
						
						//len_file.write(((Integer) (gen.getInfo(i).get("num_codons"))).toString());
						//len_file.write("\t");
						//len_file.write((Integer) (gen.getInfo(i).get("num_codons")));
						//len_file.write("\t");
					}
					fitness_file.write("\n");
					len_file.write("\n");
				} // Next generation

				// Clean up
				fitness_file.close();
				len_file.close();
				long run_end = System.currentTimeMillis();

				System.out.println("run time=" + (run_end - run_start));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} // Next run
	}

	/**
	 * Create a random solution that can be used in the initial population
	 * 
	 * @return
	 */
	protected abstract Solution createRandomSolution();

	/**
	 * Perform a random search on the problem.
	 * 
	 * @param data_path
	 * @param num_runs
	 * @param batch_size
	 * @param num_batchs
	 */
	public void randomSearch(String data_path, int num_runs, int num_batchs,
			int batch_size) {

		Writer fout = null;

		FitnessEvaluator fitness_eval = getFitnessEvaluator();

		// Do the required number of runs
		for (int run = 0; run < num_runs; run++) {		
			try {
				System.out.println("run " + run);
				long run_start = System.currentTimeMillis();
				
				// Open a new file for each run
				String path = data_path
						+ String.format("rnd/run%1$04d/",
								(int) (Math.random() * 9999));
				File file = new File(path);
				file.mkdirs();
				fout = new BufferedWriter(new FileWriter(path + "/fitness.txt"));

				// Run each batch (the equavilent to a generation)				
				for (int batch_idx = 0; batch_idx < num_batchs; batch_idx++) {
					long batch_start = System.currentTimeMillis();

					// For each solution in the batch (population)
					for (int j = 0; j < batch_size; j++) {
						// Create a new random solution
						Solution solution = createRandomSolution();

						// Evaluate the fitness
						double fitness = fitness_eval.getFitness(solution);

						// Log out the fitness
						fout.write(String.valueOf(fitness));
						fout.write("\t");

					}
					
					long batch_end = System.currentTimeMillis();
					
					if (batch_idx % 50 == 0) {
						System.out.println(batch_idx + ": time=" + (batch_end - batch_start));
					}

					
					// Next batch
					fout.write("\n");
				}
				
				long run_end = System.currentTimeMillis();
				System.out.println("run time=" + (run_end - run_start));
				System.out.println();
				
				// Cleanup
				fout.close();
			} catch (Exception e) {
				e.printStackTrace();
			}

			// Next run
		}

	}

	/**
	 * Returns the fitness evaluator to use when evalutating the fitness for
	 * this problem.
	 * 
	 * @return
	 */
	protected abstract FitnessEvaluator getFitnessEvaluator();

	public void ec(String data_path, int num_runs, int num_generations,
			int population_size, int tournament_size, int mutation_probability,
			int crossover_probability, int elites_percentage) {
		ec(data_path, num_runs, num_generations, population_size,
				tournament_size, mutation_probability, crossover_probability,
				elites_percentage, false);
	}

	public void ec(String data_path, int num_runs, int num_generations,
			int population_size) {
		ec(data_path, num_runs, num_generations, population_size, 7, 25, 75,
				10, false);
	}
	
	public void hillClimbing(String data_path, int num_runs, int num_steps,
			int batch_size) {

		Writer fout = null;

		FitnessEvaluator fitness_eval = getFitnessEvaluator();

		// Do the required number of runs
		for (int run = 0; run < num_runs; run++) {		
			try {
				System.out.println("run " + run);
				long run_start = System.currentTimeMillis();
				
				// Open a new file for each run
				String path = data_path
						+ String.format("rnd/run%1$04d/",
								(int) (Math.random() * 9999));
				File file = new File(path);
				
				file.mkdirs();
				fout = new BufferedWriter(new FileWriter(path + "/fitness.txt"));

				// Create out initial batch of random solutions
				Solution[] batch = new Solution[batch_size];
				for ( int i = 0; i < batch_size;i++) {
					batch[i] = createRandomSolution();
				}
				
				// Run each batch (the equavilent to a generation)				
				for (int step_idx = 0; step_idx < num_steps; step_idx++) {
					long batch_start = System.currentTimeMillis();

					// For each solution in the batch (population)
					for (int j = 0; j < batch_size; j++) {
						// Evaluate the fitness
						double original_fitness = fitness_eval.getFitness(batch[j]);
						
						Solution mutant = batch[j].mutate();
						double mutant_fitness = fitness_eval.getFitness(mutant);
												

						// Log out the fitness
						fout.write(String.valueOf(fitness));
						fout.write("\t");

					}
					
					long batch_end = System.currentTimeMillis();
					
					if (step_idx % 50 == 0) {
						System.out.println(step_idx + ": time=" + (batch_end - batch_start));
					}

					
					// Next batch
					fout.write("\n");
				}
				
				long run_end = System.currentTimeMillis();
				System.out.println("run time=" + (run_end - run_start));
				System.out.println();
				
				// Cleanup
				fout.close();
			} catch (Exception e) {
				e.printStackTrace();
			}

			// Next run
		}

	}

}
